﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Compiler
{
    class Graph<State, Input> // : IEnumerable<Pair<State, Input>> // where State : new() where Input : new()
    {
        public Dictionary<State, Dictionary<Input, State>> graph;
        private IEqualityComparer<State> stateCmp;
        private IEqualityComparer<Input> inputCmp;
        private Func<State, string> StatePrinter;

        public Graph(IEqualityComparer<State> stateCmp, IEqualityComparer<Input> inputCmp, Func<State, string> StatePrinter)
        {
            this.stateCmp = stateCmp;
            this.inputCmp = inputCmp;
            this.StatePrinter = StatePrinter;

            graph = new Dictionary<State, Dictionary<Input, State>>(stateCmp);
        }

        public Dictionary<State, SimpleParseTableState> CreateStateMapper()
        {
            var StateMapper = new Dictionary<State, SimpleParseTableState>(stateCmp);

            int i = 0;
            foreach (State state in graph.Keys)
                StateMapper.Add(state, new SimpleParseTableState(i++));

            return StateMapper;
        }

        //true -> neuer zustand, false -> bereits bekannt
        public bool TryAddState(State state)
        {
            if (!graph.ContainsKey(state))
            {
                graph.Add(state, new Dictionary<Input, State>(inputCmp));
                return true;
            }
            return false;
        }

        public void AddRelation(State sourceState, Input input, State followState)
        {
            //assert geht schief. kein fehler, sondern ein sauberkeitsproblem. das gefällt mir so nicht!
            //Debug.Assert(graph.ContainsKey(followState));
            graph[sourceState].Add(input, followState);
        }

        public bool TryAddRelation(State sourceState, Input input, State followState)
        {
            if (!graph[sourceState].ContainsKey(input))
            {
                graph[sourceState].Add(input, followState);
                return true;
            }
            return false;
        }

        //todo: mehrere zustände die gleich sind können unterschiedliche referenzen haben. tjaaa. ist das schlecht?
        public void CheckReferences()
        {
            foreach (var state in graph.Keys)
            {
                foreach (var map in graph.Values)
                {
                    foreach(var state2 in map.Values)
                    if (!ReferenceEquals(state, state2) && stateCmp.Equals(state, state2))
                    {
                      //  int wait = 0;
                    }
                }
            }
        }

        public Graph<State, Input> Merge(IEqualityComparer<State> EquiCmp, Func<State, State, State> Merger)
        {
            var newgraph = new Graph<State, Input>(stateCmp, inputCmp, StatePrinter);

            //repräsentant -> äquivalenzliste
            Dictionary<State, List<State>> ReprToStatelistMap = new Dictionary<State, List<State>>(EquiCmp);

            foreach (var state in graph.Keys)
            {
                List<State> stateList;
                if (!ReprToStatelistMap.TryGetValue(state, out stateList))
                {
                    stateList = new List<State>();
                    ReprToStatelistMap.Add(state, stateList);
                }
                
                stateList.Add(state);
            }

            //alter zustand -> neuer zustand
            Dictionary<State, State> SingleToMergedMap = new Dictionary<State, State>(stateCmp);

            foreach (var reprstate in ReprToStatelistMap.Keys)
            {
                State mergedState = ReprToStatelistMap[reprstate][0];
                
                for (int i = 1; i < ReprToStatelistMap[reprstate].Count; ++i)
                    mergedState = Merger(mergedState, ReprToStatelistMap[reprstate][i]);

                for (int i = 0; i < ReprToStatelistMap[reprstate].Count; ++i)
                    SingleToMergedMap.Add(ReprToStatelistMap[reprstate][i], mergedState);

                bool done = newgraph.TryAddState(mergedState);
                Debug.Assert(done);
            }

            //Relation aufbauen
            foreach (var pair in GetPairEnumerator())
            {
                if (!newgraph.TryAddRelation(
                        SingleToMergedMap[pair.Item1],
                        pair.Item2,
                        SingleToMergedMap[FollowState(pair)]))
                {
                    //nur Assertionscheiß. Kann rausgenommen werden nach ein paar Wochen.
                    var x1 = newgraph.FollowState(new Pair<State, Input>(SingleToMergedMap[pair.Item1], pair.Item2));
                    var x2 = SingleToMergedMap[FollowState(pair)];
                    Debug.Assert(stateCmp.Equals(x1, x2));
                }
            }
          
            return newgraph;
        }

        public override string ToString()
        {
            var stateMapper = CreateStateMapper();
            StringBuilder b = new StringBuilder();

            b.Append("-------States-------\n\n");
            foreach (var state in graph.Keys)
            {
                //b.Append("Count: " + cnt++ + "\n");
                b.Append("---State " + stateMapper[state] + "---\n");
                b.Append(StatePrinter(state));
                b.Append("\n\n");
            }

            b.Append("\n-------Relations-------\n\n");
            foreach (var state in graph.Keys)
            {   
                //b.Append("Count: " +cnt++ + "\n");
                //b.Append(StatePrinter(state));
                b.Append("---State " + stateMapper[state] + "---\n");
                foreach (var input in graph[state].Keys)
                {
                    b.Append(" " + input.ToString() + "  --->  " + stateMapper[graph[state][input]].ToString());// StatePrinter(graph[state][input]));
                    b.Append("\n");
                }
                b.Append("\n\n");
            }

            return b.ToString();
        }


        public State FollowState(Pair<State, Input> pair)
        {
            return graph[pair.Item1][pair.Item2];
        }

        public IEnumerable<Pair<State, Input>> GetPairEnumerator()
        {
            foreach (var state in graph.Keys)
                foreach (var input in graph[state].Keys)
                    yield return new Pair<State, Input>(state, input);
        }

        public IEnumerable<State> GetStateEnumerator()
        {
            foreach (var state in graph.Keys)
                yield return state;
        }
    }
}
